package com.gper.edu.vip.struct.proxy.impl;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * @Author: ellis.guan
 * @Description: 自己实现JDK动态代理
 * @Date: 2020/3/2 17:51
 * @Version: 1.0
 */
public class EllisProxy {
    private static final String ln = "\r\n";

    //要生成接口代理类
    public static Object newProxyInstance(EllisClassLoader loader, Class<?>[] interfaces,EllisInvocationHandler h)
            throws IllegalArgumentException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
        String proxyClassName = "$EllisProxy"+(new Random()).nextInt(100000);
        //1. 动态生成源码.java文件
        String javaFileContent = generateJava(proxyClassName,interfaces);
        //2. Java文件输出到磁盘

        String filePath = EllisProxy.class.getResource("").getPath();
        File file = new File(filePath+proxyClassName+".java");
        writeJavaContentToFile(javaFileContent, file);
        //3. 把.java文件编译成.class文件
        compilerJavaFile(file);
        //4. 加载class
        Class proxyClass = loader.findClass(proxyClassName);
        Constructor constructor = proxyClass.getConstructor(EllisInvocationHandler.class);
        //5. 返回新的代理类对象
        return constructor.newInstance(h);
    }

    private static void compilerJavaFile(File file) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
        try {
            Iterable iterable = manager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
        }catch (Exception e){
            throw new RuntimeException(e.getMessage(),e);
        }finally {
            try{
                file.delete();
                manager.close();
            }catch (IOException e){
                throw e;
            }
        }
    }

    private static void writeJavaContentToFile(String javaFileContent, File file) {
        FileWriter writer = null;
        try {
            writer = new FileWriter(file);
            writer.write(javaFileContent);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static String generateJava(String proxyClassName,Class<?>[] interfaces){
        StringBuilder builder = new StringBuilder();
        builder.append("package com.gper.edu.vip.struct.proxy.impl;").append(ln)
                .append("import java.lang.reflect.*;").append(ln);
        //拼接要实现的接口
        String[] interfaceNames = new String[interfaces.length];
        for (int i = 0; i < interfaces.length; i++) {
            interfaceNames[i] = interfaces[i].getName();
            builder.append("import ").append(interfaces[i].getName()).append(";").append(ln);
        }
        builder.append("public final class ").append(proxyClassName).append(" extends EllisProxy").append(ln);
        builder.append(" implements ").append(Arrays.stream(interfaceNames).collect(Collectors.joining(","))).append(ln);
        builder.append("{").append(ln);
        builder.append(" EllisInvocationHandler h;").append(ln);
        builder.append("public ").append(proxyClassName).append("(EllisInvocationHandler h)").append(ln)
                .append("{").append(ln)
                .append("     this.h = h;").append(ln)
                .append("}").append(ln);
        //实现所有接口中的方法
        for (int i = 0; i < interfaces.length; i++) {
            generateClassMethod(interfaces[i], builder);
        }
        builder.append("}");
        return builder.toString();
    }

    private static void generateClassMethod(Class<?> anInterface, StringBuilder builder) {
        //获取接口所有的方法
        Method[] methods = anInterface.getDeclaredMethods();
        for (int j = 0; j < methods.length; j++) {
            Class<?> returnType = methods[j].getReturnType();
            Class<?>[] paramTypes = methods[j].getParameterTypes();
            //保存参数类型对应的类名及参数变更名
            String[] paramClassNames = new String[paramTypes.length];
            String[] vars = new String[paramTypes.length];
            //参数类名及对应参数值命名
            for (int k = 0; k < vars.length; k++) {
                vars[k] = "var"+k;
                paramClassNames[k] = paramTypes[k].getName();
            }
            builder.append("public ").append(returnType.getName()).append(" ")
                    .append(methods[j].getName()).append("(");
            String[] paramNameAndValues = new String[paramTypes.length];
            //参数类名及参数变更名拼接
            for (int k = 0; k < paramClassNames.length; k++) {
                paramNameAndValues[k] = paramClassNames[k]+" "+vars[k];
            }
            //拼接方法中的参数
            builder.append(Arrays.stream(paramNameAndValues).collect(Collectors.joining(","))).append(")")
            .append("{").append(ln)
            .append("try{").append(ln)
                    .append("   Method m = ").append(anInterface.getName()).append(".class.getMethod(")
                        .append("\"").append(methods[j].getName()).append("\"").append(",new Class[]{")
                            .append(Arrays.stream(paramClassNames).map(s -> s+".class").collect(Collectors.joining(",")))
                            .append("}")
                        .append(");").append(ln)
                    .append("   Object result = this.h.invoke(this,m,new Object[]{")
                        .append(Arrays.stream(vars).collect(Collectors.joining(",")))
                        .append("});").append(ln);
            //处理返回，如果有返回值，则要进行类型转换
            if (returnType.getName() == "void") {
                builder.append("return;").append(ln);
            }else {
                builder.append("  return (").append(returnType.getName()).append(") result;").append(ln);
            }
            builder.append("} catch (RuntimeException | Error error2) {").append(ln)
                .append("   throw error2;").append(ln)
                .append("} catch (Throwable error3) {").append(ln)
                .append("   throw new UndeclaredThrowableException(error3);").append(ln)
                .append("}").append(ln)
                .append("}").append(ln);
        }
    }
}
